文章目录
  1. 1. 一、消息队列的应用点
  2. 2. 二、消息队列的设计
  3. 3. 三、Pull和Push

《消息队列设计精要》
原文链接:https://tech.meituan.com/mq-design.html

一、消息队列的应用点

  • 业务解耦:对于非核心的事务,做到“通知”,而非全部处理“完成”。
  • 最终一致性:

    • 强一致性:分布式事务(落地难成本高)
    • 最终一致性:”记录“和”补偿“。记录不确定事件,到达成功状态后再清理。可以依靠本地定时任务轮询不断重发。
    • 最终一致性不是消息队列必须的
  • 广播:只需关心消息是否送达队列,其他事情交给下游的订阅者

  • 错峰流量控制:平衡前后端处理能力的巨大差异

二、消息队列的设计

  • RPC通信协议:

    • 本质:两次或者三次RPC加中间broker的转储
    • 尽量选择现有框架;尽量选择本机房投递
  • 高可用:

    • 目标:保证broker接受和确认消息是幂等的
    • 方式:共享存储,多机器共享db
  • 服务端承载消息堆积:

    • 持久化
    • 非持久化
    • 存储方式:内存,分布式KV,数据库,磁盘
  • 存储子系统选择:可靠性要求高则选择DB

  • 消费关系解析:点对点和广播
  • 可靠投递:

    • 最终一致性保证导致消息可能重复,异常情况下可能延迟。
    • 重复消息怎么解决:

      • 鉴别重复(版本号)
      • 幂等处理
      • 尽量减少重复消息投递
    • 顺序消息怎么实现:

      • 版本号(保存各个消息到来的顺序,重组)
      • 状态机
    • 总结: 保证不丢失消息的情况下尽量减少重复消息,消费顺序交给消费者保证

  • 异步和同步

    • 服务端异步:解放I/O
    • 返回future的方式不一定只有线程池:NIO、事件
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      //客户端异步,服务端异步
      Future<Result> future = request(server);
      return future;
      //客户端异步,服务端同步(开线程池)
      Future<Result> future = executor.submit(new Callable(){public void call<Result>(){
      result = request(server);
      }})
      return future;
      //客户端同步,服务端异步
      Future<Result> future = request(server);
      synchronized(future){
      while(!future.isDone()){
      future.wait();
      }
      }
      return future.get();
      //客户端同步,服务端同步
      Result result = request(server);
  • 网络请求小包合并成大包会提高性能

    • 减少无谓的请求头
    • 减少回复的ACK数量

三、Pull和Push

  • Push的缺点:慢消费
  • Pull的缺点:消息延迟与忙碌等待
  • Pull对于顺序消息的实现容易很多:
    • producer对应partition,单线程
    • consumer对应partition,消费确认后继续
文章目录
  1. 1. 一、消息队列的应用点
  2. 2. 二、消息队列的设计
  3. 3. 三、Pull和Push